package com.changgou.search.service.impl;

import com.alibaba.fastjson.JSON;
import com.changgou.entity.PageResult;
import com.changgou.search.pojo.SkuInfo;
import com.changgou.search.service.SearchService;
import org.apache.commons.lang.StringUtils;
import org.elasticsearch.action.search.SearchResponse;
import org.elasticsearch.index.query.BoolQueryBuilder;
import org.elasticsearch.index.query.Operator;
import org.elasticsearch.index.query.QueryBuilders;
import org.elasticsearch.search.SearchHit;
import org.elasticsearch.search.SearchHits;
import org.elasticsearch.search.aggregations.Aggregation;
import org.elasticsearch.search.aggregations.AggregationBuilders;
import org.elasticsearch.search.aggregations.bucket.terms.StringTerms;
import org.elasticsearch.search.fetch.subphase.highlight.HighlightBuilder;
import org.elasticsearch.search.fetch.subphase.highlight.HighlightField;
import org.elasticsearch.search.sort.SortBuilder;
import org.elasticsearch.search.sort.SortBuilders;
import org.elasticsearch.search.sort.SortOrder;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Pageable;
import org.springframework.data.elasticsearch.core.ElasticsearchTemplate;
import org.springframework.data.elasticsearch.core.SearchResultMapper;
import org.springframework.data.elasticsearch.core.aggregation.AggregatedPage;
import org.springframework.data.elasticsearch.core.aggregation.impl.AggregatedPageImpl;
import org.springframework.data.elasticsearch.core.query.NativeSearchQuery;
import org.springframework.data.elasticsearch.core.query.NativeSearchQueryBuilder;
import org.springframework.data.elasticsearch.core.query.SearchQuery;
import org.springframework.stereotype.Service;

import java.util.*;
import java.util.stream.Collectors;

@Service
public class SearchServiceImpl implements SearchService {
    //使用springdataelasticsearch技术进行全文检索
    @Autowired
    private ElasticsearchTemplate template;

    @Override
    public Map searchQuery(Map<String, String> searchMap) {
        Map<String, Object> resultMap = new HashMap<>();

        //判断searchMap是否为空
        if (searchMap != null) {
//第一步：创建 NativeSearchQueryBuilder 多条件查询方法
            NativeSearchQueryBuilder nativeSearchQueryBuilder = new NativeSearchQueryBuilder();
            //构建boolQuery对象
            BoolQueryBuilder boolQuery = QueryBuilders.boolQuery();

    //第一个功能：根据关键字查询索引库
            //判断是否有关键字
            if (StringUtils.isNotEmpty(searchMap.get("keywords"))) {//有关键字
                //matchQuery表示模糊匹配
                boolQuery.must(QueryBuilders.matchQuery("name", searchMap.get("keywords")).operator(Operator.AND));
            }

    //第二个功能：品牌过滤查询条件
            if (StringUtils.isNotEmpty(searchMap.get("brand"))) {
                //termQuery表示分词精确查询
                boolQuery.filter(QueryBuilders.termQuery("brandName", searchMap.get("brand")));
            }

    //第三个功能：规格过滤条件,比如：spec_屏幕尺寸
            for (String key : searchMap.keySet()) {
                //表示是规格
                if (key.equals("spec_")) {
                    //获取规格值
                    String value = searchMap.get(key);
                    //域名需要拼接specMap.屏幕尺寸.keyword
                    boolQuery.filter(QueryBuilders.termQuery("specMap." + key.substring(5) + ".keyword", value));
                }
            }

    //第四个功能：价格区间过滤条件
            if (StringUtils.isNotEmpty(searchMap.get("price"))) {//有价格关键字
                //以 “-” 号分割为数组
                String[] prices = searchMap.get("price").split("-");
                if (prices.length == 2) {//3000以下的价格
                    //小于第二个数字
                    boolQuery.filter(QueryBuilders.rangeQuery("price").lt(prices[1]));
                }
                //否则大于第一个数字
                boolQuery.filter(QueryBuilders.rangeQuery("price").gt(prices[0]));
            }

    //第五个功能：分页查询
            String pageNum = searchMap.get("pageNum");
            String pageSize = searchMap.get("pageSize");
            if (pageNum == null) {
                pageNum = "1";
            }
            if (pageSize == null) {
                pageSize = "30";
            }
            nativeSearchQueryBuilder.withPageable(PageRequest.of(Integer.parseInt(pageNum) - 1, Integer.parseInt(pageSize)));

    //第六个功能：搜索排序
            //sortFiled表示排序字段域，sortRule表示排序规则，ASC和DESC
            if (StringUtils.isNotEmpty(searchMap.get("sortFiled"))) {
                if ("ASC".equals(searchMap.get("sortRule"))) {
                    //升序
                    nativeSearchQueryBuilder.withSort(SortBuilders.fieldSort(searchMap.get("sortFiled")).order(SortOrder.ASC));
                } else {
                    nativeSearchQueryBuilder.withSort(SortBuilders.fieldSort(searchMap.get("sortFiled")).order(SortOrder.DESC));
                }
            }

    //第七个功能：高亮显示
            HighlightBuilder.Field field = new HighlightBuilder
                    .Field("name")
                    .preTags("<span style='color:red'>")
                    .postTags("</span>");
            nativeSearchQueryBuilder.withHighlightFields(field);

//第二步：添加查询条件
            //1.关键字查询
            nativeSearchQueryBuilder.withQuery(boolQuery);
            //2.品牌聚合查询
            String skuBrand = "skuBrand";
            nativeSearchQueryBuilder.addAggregation(AggregationBuilders.terms(skuBrand).field("brandName"));
            //3.规格聚合查询----定义规格聚合名称,目的是为了根据skuSpec去查询
            String skuSpec = "skuSpec";
            nativeSearchQueryBuilder.addAggregation(AggregationBuilders.terms(skuSpec).field("spec.keyword"));

//第三步：执行查询，还回结果
            SearchQuery searchQuery = nativeSearchQueryBuilder.build();
            AggregatedPage<SkuInfo> skuInfos = template.queryForPage(searchQuery, SkuInfo.class, new SearchResultMapper() {
                @Override
                public <T> AggregatedPage<T> mapResults(SearchResponse searchResponse, Class<T> aClass, Pageable pageable) {
                    //创建一个List集合用来承载搜索出来的结果
                    List list = new ArrayList();
                    //获取命中结果
                    SearchHits hits = searchResponse.getHits();
                    //判断结果是否为空
                    if (hits != null) {
                        for (SearchHit hit : hits) {
                            //把每个命中数据转化成skuinfo对象
                            SkuInfo skuInfo = JSON.parseObject(hit.getSourceAsString(), SkuInfo.class);
                            //高亮设置
                            Map<String, HighlightField> highlightFields = hit.getHighlightFields();
                            if (highlightFields != null && highlightFields.size() > 0){
                                skuInfo.setName(highlightFields.get("name").getFragments()[0].toString());
                            }
                            //添加到list集合中
                            list.add(skuInfo);
                        }
                    }
                    /**
                     * 参数1表示搜索结果list
                     * 参数2表示分页对象
                     * 参数3表示获取命中结果总记录数
                     * 参数4表示响应的聚合查询
                     */
                    return new AggregatedPageImpl<>(list, pageable, hits.getTotalHits(), searchResponse.getAggregations());
                }
            });
//第四步：数据封装
            //1.关键字查询封装
            resultMap.put("total", skuInfos.getTotalElements());//总记录数
            resultMap.put("totalPages", skuInfos.getTotalPages());//总页数
            resultMap.put("skuList", skuInfos.getContent());//数据列表
            //2.获取品牌聚合查询结果集并且进行封装
            StringTerms brandTerms = (StringTerms) skuInfos.getAggregation(skuBrand);
            List<String> brandList = brandTerms.getBuckets().stream().map(bucket -> bucket.getKeyAsString()).collect(Collectors.toList());
            resultMap.put("brandList", brandList);
            //3.获取规格聚合查询结果集并且进行封装
            StringTerms specTerms = (StringTerms) skuInfos.getAggregation(skuSpec);
            List<String> specList = specTerms.getBuckets().stream().map(bucket -> bucket.getKeyAsString()).collect(Collectors.toList());
            resultMap.put("specList", this.specFormate(specList));
            //4.封装当前页码
            resultMap.put("pageNum", pageNum);
            return resultMap;

        }





        /*Map<String, Object> resultMap = new HashMap<>();
        //第一个功能：根据关键字搜索
        if (searchMap != null) {
            //1， 创建
            NativeSearchQueryBuilder nativeSearchQueryBuilder = new NativeSearchQueryBuilder();
            //2. 构建boolQuery对象
            BoolQueryBuilder boolQueryBuilder = QueryBuilders.boolQuery();
            //2.1 按照关键字搜索
            if (StringUtils.isNotEmpty(searchMap.get("keywords"))) {//有搜索关键字
                //matchQuery表示模糊匹配，参数1表示域名
                boolQueryBuilder.must(QueryBuilders.matchQuery("name",
                        searchMap.get("keywords")).operator(Operator.AND));
            }
            //2.2品牌过滤查询条件
            if (StringUtils.isNotEmpty(searchMap.get("brand"))) {
                boolQueryBuilder.filter(QueryBuilders.termQuery("brandName", searchMap.get("brand")));
            }
            //2.3规格过滤条件,spec_屏幕尺寸
            for (String key : searchMap.keySet()) {
                if (key.startsWith("spec_")) {//表示是规格
                    //获取规格值
                    String value = searchMap.get(key).replace(" ", "+");
                    //域名需要拼接specMap.屏幕尺寸.keyword
                    boolQueryBuilder.filter(QueryBuilders.termQuery("specMap." + key.substring(5) + ".keyword", value));
                }
            }
            //2.4 价格区间过滤条件
            if (StringUtils.isNotEmpty(searchMap.get("price"))) {//有价格关键字
                String[] prices = searchMap.get("price").split("-");
                if (prices.length == 2) {//3000以下的价格
                    //小于第二个数字
                    boolQueryBuilder.filter(QueryBuilders.rangeQuery("price").lt(prices[1]));
                }
                //否则大于第一个数字
                boolQueryBuilder.filter(QueryBuilders.rangeQuery("price").gt(prices[0]));
            }
            //3.添加查询条件
            nativeSearchQueryBuilder.withQuery(boolQueryBuilder);
            //3.1 品牌聚合查询
            //定义品牌聚合名称,目的是为了根据skuBrand去查询
            String skuBrand = "skuBrand";
            nativeSearchQueryBuilder.addAggregation(AggregationBuilders.terms(skuBrand).field("brandName"));
            //3.2 规格聚合查询
            //定义规格聚合名称,目的是为了根据skuSpec去查询
            String skuSpec = "skuSpec";
            nativeSearchQueryBuilder.addAggregation(AggregationBuilders.terms(skuSpec).field("spec.keyword"));
            //3.3分页查询
            String pageNum = searchMap.get("pageNum");
            String pageSize = searchMap.get("pageSize");
            if (StringUtils.isEmpty(pageNum)) {
                pageNum = "1";
            }
            if (StringUtils.isEmpty(pageSize)) {
                pageSize = "30";
            }
            nativeSearchQueryBuilder.withPageable(PageRequest.of(Integer.parseInt(pageNum) - 1, Integer.parseInt(pageSize)));
            //3.4 排序查询    sortFiled表示排序字段域，sortRule表示排序规则，ASC和DESC
            if (StringUtils.isNotEmpty(searchMap.get("sortFiled"))) {
                if (searchMap.get("sortRule").equals("ASC")) {//升序
                    nativeSearchQueryBuilder.withSort(SortBuilders.fieldSort(searchMap.get("sortFiled")).order(SortOrder.ASC));
                } else {
                    nativeSearchQueryBuilder.withSort(SortBuilders.fieldSort(searchMap.get("sortFiled")).order(SortOrder.DESC));
                }
            }
            //3.5 高亮查询显示
            HighlightBuilder.Field field = new HighlightBuilder.
                    Field("name")
                    .preTags("<span style='color:red'>")
                    .postTags("</span>");
            nativeSearchQueryBuilder.withHighlightFields(field);
            //4.执行查询，返回结果
            AggregatedPage<SkuInfo> skuInfos = template.queryForPage(nativeSearchQueryBuilder.build(),
                    SkuInfo.class, new SearchResultMapper() {
                        @Override
                        public <T> AggregatedPage<T> mapResults(SearchResponse searchResponse, Class<T> aClass, Pageable pageable) {
                            //创建一个List集合用来承载搜索出来的结果
                            List list = new ArrayList();
                            //获取命中结果
                            SearchHits hits = searchResponse.getHits();
                            if (hits != null) {
                                for (SearchHit hit : hits) {
                                    //把每个命中数据转化成skuinfo对象
                                    SkuInfo skuInfo = JSON.parseObject(hit.getSourceAsString(), SkuInfo.class);
                                    //高亮设置
                                    Map<String, HighlightField> fieldMap = hit.getHighlightFields();
                                    if (fieldMap.size() > 0 && fieldMap != null) {
                                        skuInfo.setName(fieldMap.get("name").getFragments()[0].toString());
                                    }
                                    //添加到list集合中
                                    list.add(skuInfo);
                                }
                            }
                            *//**
         * 参数1表示搜索结果list
         * 参数2表示分页对象
         * 参数3表示获取命中结果总记录数
         * 参数4表示响应的聚合查询
         *//*
                            return new AggregatedPageImpl<>(list, pageable, hits.getTotalHits(),
                                    searchResponse.getAggregations());
                        }
                    });
            //5.数据封装
            resultMap.put("total", skuInfos.getTotalElements());//总记录数
            resultMap.put("totalPages", skuInfos.getTotalPages());//总页数
            resultMap.put("skuList", skuInfos.getContent());//数据列表
            //获取品牌聚合查询结果集并且进行封装
            StringTerms brandTerms = (StringTerms) skuInfos.getAggregation(skuBrand);
            List<String> brandList = brandTerms.getBuckets().stream().map(bucket -> bucket.getKeyAsString()).collect(Collectors.toList());
            resultMap.put("brandList", brandList);
            //获取规格聚合查询结果集并且进行封装
            StringTerms skuSpecTerms = (StringTerms) skuInfos.getAggregation(skuSpec);
            List<String> specList = skuSpecTerms.getBuckets().stream().map(bucket -> bucket.getKeyAsString()).collect(Collectors.toList());
            resultMap.put("specList", specList);
            //封装当前页码
            resultMap.put("pageNum", pageNum);
            return resultMap;
        }*/
        return null;
    }

    //转化规格格式
    public Map<String, Set<String>> specFormate(List<String> specList) {
        //定义一个map集合用来封装转化之后的规格
        Map<String, Set<String>> resultMap = new HashMap<>();
        if (specList != null && specList.size() > 0) {
            for (String spec : specList) {
                //把json字符串转化成map
                Map<String,String> specMap = JSON.parseObject(spec, Map.class);
                for (String specKey : specMap.keySet()) {
                    Set<String> specSet = resultMap.get(specKey);
                    if (specSet==null){
                        specSet = new HashSet<>();
                    }
                    specSet.add(specMap.get(specKey));
                    //将set封装到map中
                    resultMap.put(specKey,specSet);
                }
            }
        }
        return resultMap;
    }
}
